// storage.js
// Definition of the storage object

var storage = 
{
	get : (function(item)
	{
		return localStorage.getItem(item);
	}), 
	
	getAsync : (function(item_or_items, callback)
	{
		if(this.useChromeStorage)
			chrome.storage.local.get(item_or_items, callback);
		else
		{
			var items = {};
			if(typeof(item_or_items) == "string")
				items[item_or_items] = this.get(item);
			else
				item_or_items.forEach(function(cur)
				{
					items[cur] = this.get(cur);
				});
			callback(items);
		}
	}), 

	set : (function(item, newValue, syncable, large)
	{
		var oldValue = this.get(item);
		localStorage.setItem(item, newValue);
		if(this.useChromeStorage)
		{
			var items = {};
			items[item] = newValue;
			chrome.storage.local.set(items);
			if(syncable !== false)
			{
				if(large)
					this.setSyncLarge(item, newValue);
				else
					this.queueSyncStorageChange(item, newValue);
			}
			//NO notification required after local/sync.set since we handle
			//chrome.storage.onChanged
		}
		else if(this.onChanged && oldValue != newValue)
			this.onChanged(item, newValue, oldValue);
	}), 

	setSyncLarge : (function(item, value)
	{
		//Calculate a chunk size that is guaranteed within the quota
		//We just use a simple algorithm so that all chunks are of the
		//same size.
		var chunkSize = 2000 - item.length;
		var chunks = [];
		while(value)
		{
			chunks.push(value.substr(0, chunkSize));
			value = value.slice(chunkSize);
		}
		for(var i = 0; i < chunks.length; ++i)
		{
			var curValue;
			if(i == 0)
				curValue = {c:chunks.length, v:chunks[i]};
			else
				curValue = chunks[i];
			this.queueSyncStorageChange(item + "~" + i.toString(), 
				curValue);
		}
	}), 

	getSyncLarge : (function(item, callback)
	{
		var item0 = item + "~0";
		chrome.storage.sync.get(item0, function(items)
		{
			var count = items[item0].c;
			var value = items[item0].v;
			var remaining_items = [];
			for(var i = 1; i < count; ++i)
				remaining_items.push(item + "~" + i.toString());
			chrome.storage.sync.get(remaining_items, function(items2)
			{
				for(var i = 1; i < count; ++i)
					value += items2[item + "~" + i.toString()];
				callback(value);
			});
		});
	}), 

	remove : (function(item, syncable, large)
	{
		var oldValue = localStorage[item];
		localStorage.removeItem(item);
		if(this.useChromeStorage && syncable !== false)
			chrome.storage.sync.remove(item);
		else if(this.onChanged && oldValue != undefined)
			this.onChanged(item, undefined, oldValue);
	}), 

	canSync : (function()
	{
		return chrome.storage && chrome.storage.sync && 
			chrome.storage.onChanged;
	}), 

	queuedSyncStorageChanges : {}, 
	
	queueSyncStorageChange : (function(item, value)
	{
		this.queuedSyncStorageChanges[item] = value;
	}), 

	// Sync queuedSyncStorageChanges with chrome.storage.sync. This 
	// function should be called periodically (but don't exceed the 
	// throttling limits).
	updateSyncStorage: (function()
	{
		var changes = this.queuedSyncStorageChanges;
		chrome.storage.sync.set(changes, (function()
		{
			if(!chrome.extension.lastError)
				for(var key in this.queuedSyncStorageChanges)
					if(changes[key] == this.queuedSyncStorageChanges)
						delete this.queuedSyncStorageChanges[key];
		}));
	}), 

	useChromeStorage : false, 
	init : (function()
	{
		//Initialization is necessary only when chrome.storage is used.
		if(!this.canSync())
			return;
		this.useChromeStorage = true;

		chrome.storage.local.get(null, function(items)
		{
			if(Object.keys(items).length == 0)
			{
				var newItems = {};
				for(var key in localStorage)
					newItems[key] = localStorage[key];
				chrome.storage.local.set(newItems);
			}
		});

		// Handling chrome.storage.onChanged to notify setting changes
		// Note that chrome.storage.onChanged will not fire if a `set` 
		// method is called with the same item value as before, which is
		// consistent with the behavior of storage.onChanged.
		// Note that this event will also fire if settings are changed
		// because of sync. That's why we listen for it.
		chrome.storage.onChanged.addListener(this.onChromeStorageChanged);
	}), 

	workaroundChromeStorageChanges : (function (changes, prefix, result)
	{
		for(var key in changes)
		{
			var cur = changes[key];
			var full_key = prefix + key;

			//A normal StorageChange item
			//Note that this assumes that neither 'newValue' nor 
			//'oldValue' may appear in the name of a storage item
			if(cur.newValue || cur.oldValue || Object.keys(cur).length == 0)
				result[full_key] = cur;
			else
				transformChanges(changes[key], full_key + ".", result);
		}
	}), 

	onChromeStorageChanged : (function(changes_chrome, namespace)
	{
		console.log("onChromeStorageChanged", changes_chrome, 
			namespace);//DEBUG
		var changes = {};
		storage.workaroundChromeStorageChanges(changes_chrome, "", changes);
		console.log("onChromeStorageChanged", changes, namespace);//DEBUG
		if(namespace == "local")
		{
			for(var item in changes)
			{
				console.log(item);
				//Prevent notifications when first initialize
				storage.get(item) != changes[item].newValue && 
					storage.onChanged && storage.onChanged(item, 
					changes[item].newValue, changes[item].oldValue);
			}
		}
		else if(namespace == "sync")
		{
			var syncedLargeItems = [];
			//Sync changes 
			for(var item in changes)
			{
				if(item.indexOf("~") != -1)
				{
					var real_name = item.substr(0, item.indexOf("~"));
					if(syncedLargeItems.indexOf(real_name) == -1)
					{
						syncedLargeItems.push(real_name);
						storage.getSyncLarge(real_name, function(value)
						{
							storage.set(item, value, 
								false, //No need to set chrome.storage.sync
										//again
								false //large or not, it does not matter.
							);
						});
					}
				}
				else if(storage.get(item) != changes[item])
					storarge.set(item, changes[value], 
						false, //No need to set	chrome.storage.sync again
						false //Not a large item
					);
			}
		}
	}), 

	//DEBUG DEBUG
	_clearLocal: (function()
	{
		chrome.storage.local.get(null, function(items)
		{
			chrome.storage.local.remove(Object.keys(items));
		});
	}), 

	//DEBUG DEBUG
	_dumpLocal: (function()
	{
		chrome.storage.local.get(null, function(items)
		{
			console.log(items);
		});
	}), 

	_fromBoolean : (function(value, defaultValue)
	{
		if(defaultValue) //defaults to true
			return value != "0" && value != "false";
		else //defaults to false
			return value == "1" || value == "true";
	}), 
	_toBoolean : (function(value, defaultValue)
	{
		if(defaultValue) //defaults to true
			return value ? null : "0";
		else //defaults to false
			return value ? "1" : null;
	}), 

	_fromString : (function(value, defaultValue)
	{
		return value || defaultValue;
	}), 
	_toString : (function(value, defaultValue)
	{
		return value == defaultValue ? null : value;
	}), 

	_fromDefaultEmptyArray : (function(value)
	{
		return value ? JSON.parse(value) : [];
	}), 
	_toDefaultEmptyArray : (function(value)
	{
		return (!value || value.length == 0) ? null : 
			JSON.stringify(value);
	}), 

	_fromObject : (function(value)
	{
		return value ? JSON.parse(value) : {};
	}), 
	_toObject : (function(value)
	{
		return Object.keys(value).length == 0 ? null : 
			JSON.stringify(value);
	}), 

	_fromInteger : (function(value, defaultValue)
	{
		return value ? parseInt(value) : defaultValue;
	}), 
	_toInteger : (function(value, defaultValue)
	{
		return value == defaultValue ? null : value.toString();
	}), 
};

